package com.clp.protocol.core.client;

import com.clp.protocol.core.utils.AssertUtil;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 容器
 * 1、不允许 null 元素
 * 2、不允许重复元素，使用 equals() & hashCode() 比较相同
 * 3、线程安全
 * 4、适合写少读多情景
 *
 * @param <M> 元素
 */
@Slf4j
public class Container<M> implements Set<M> {
    /**
     * 使用 {@link ReentrantReadWriteLock} 的原因：一般添加、删除的操作频率极少；而查询频率较高
     */
    protected final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    protected final Set<M> elements = new HashSet<>(16);

    public Container() {
    }

    @Override
    public int size() {
        lock.readLock().lock();
        int size;
        try {
            size = elements.size();
        } finally {
            lock.readLock().unlock();
        }
        return size;
    }

    /**
     * 判断容器是否为空
     * @return
     */
    @Override
    public boolean isEmpty() {
        lock.readLock().lock();
        boolean isEmpty;
        try {
            isEmpty = elements.isEmpty();
        } finally {
            lock.readLock().unlock();
        }
        return isEmpty;
    }

    /**
     * 判断是否含有某一个元素
     * @param element 要判断的元素
     * @return 如果存在，返回true；否则，返回false
     */
    @Override
    public boolean contains(@Nullable Object element) {
        if (element == null) return false;

        lock.readLock().lock();
        boolean isContain;
        try {
            isContain = elements.contains(element);
        } finally {
            lock.readLock().unlock();
        }

        return isContain;
    }

    /**
     * 判断容器中是否有满足条件的元素
     * @param condition 条件
     * @return
     */
    public boolean contains(@Nullable Predicate<M> condition) {
        Objects.requireNonNull(condition);

        lock.readLock().lock();
        boolean isContain = false;
        try {
            for (M element : elements) {
                if (condition.test(element)) {
                    isContain = true;
                    break;
                }
            }
        } finally {
            lock.readLock().unlock();
        }

        return isContain;
    }

    @Override
    public boolean containsAll(@Nullable Collection<?> c) {
        AssertUtil.nonNull(c);
        for (Object elem : c) {
            if (elem == null) {
                return false;
            }
        }

        boolean isContainAll;
        lock.readLock().lock();
        try {
            isContainAll = elements.containsAll(c);
        } finally {
            lock.readLock().unlock();
        }

        return isContainAll;
    }

    @Override
    public Object[] toArray() {
        lock.readLock().lock();
        Object[] array;
        try {
            array = elements.toArray();
        } finally {
            lock.readLock().unlock();
        }

        return array;
    }

    @Override
    public <T> T[] toArray(@Nullable T[] a) {
        AssertUtil.nonNull(a);

        lock.readLock().lock();
        T[] array;
        try {
            array = elements.toArray(a);
        } finally {
            lock.readLock().unlock();
        }

        return array;
    }

    /**
     * 添加新的元素。
     *
     * @param m 要添加的新元素
     * @return 如果要添加的元素为null 或 元素已经存在，则添加失败；否侧，添加成功
     */
    @Override
    public boolean add(@Nullable M m) {
        if (m == null) return false;

        lock.writeLock().lock();
        boolean isAdded = false;
        try {
            if (!elements.contains(m)) {
                isAdded = elements.add(m);
            }
        } finally {
            lock.writeLock().unlock();
        }
        return isAdded;
    }

    @Override
    public boolean addAll(@Nullable Collection<? extends M> c) {
        AssertUtil.nonNullIncludingElements(c);

        boolean isAddAll;
        lock.writeLock().lock();
        try {
            isAddAll = elements.addAll(c);
        } finally {
            lock.writeLock().unlock();
        }

        return isAddAll;
    }

    /**
     * 根据条件查找元素
     * @param condition 条件
     * @return 如果满足条件的元素数量为0，则返回null；如果数量大于0，则抛出 {@link UnexpectNumberException} 异常
     */
    @Nullable
    public M getOne(@Nullable Predicate<M> condition) {
        AssertUtil.nonNull(condition, "The condition for finding one element in container must not be null!");

        lock.readLock().lock();
        M elem = null;
        int count = 0;
        try {
            for (M element : elements) {
                if (condition.test(element)) {
                    if (++count >= 2) break;
                    elem = element;
                }
            }
        } finally {
            lock.readLock().unlock();
        }
        if (count >= 2) {
            throw new UnexpectNumberException(1, 2);
        }

        return elem;
    }

    /**
     * 根据条件查找多个元素
     * @param condition 条件
     * @return 元素列表
     */
    public List<M> getMany(@Nullable Predicate<M> condition) {
        AssertUtil.nonNull(condition, "The condition for finding elements in container must not be null!");

        lock.readLock().lock();
        List<M> elems;
        try {
            elems = elements.stream().filter(condition).collect(Collectors.toList());
        } finally {
            lock.readLock().unlock();
        }

        return elems;
    }

    /**
     * 移除元素
     * @param o 要移除的元素
     * @return 如果元素为null或不存在，则移除失败；否则，移除成功
     */
    @Override
    public boolean remove(@Nullable Object o) {
        if (o == null) return false;

        boolean isRemoved;
        lock.writeLock().lock();
        try {
            isRemoved = elements.remove(o);
        } finally {
            lock.writeLock().unlock();
        }
        return isRemoved;
    }

    @Override
    public boolean removeAll(@Nullable Collection<?> c) {
        AssertUtil.nonNull(c);

        boolean isRemoveAll;
        lock.writeLock().lock();
        try {
            List<?> nonNullElems = c.stream().filter(Objects::nonNull).collect(Collectors.toList());
            isRemoveAll = elements.removeAll(nonNullElems);
        } finally {
            lock.writeLock().unlock();
        }

        return isRemoveAll;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void clear() {
        lock.writeLock().lock();
        try {
            elements.clear();
        } finally {
            lock.writeLock().unlock();
        }
    }

    @Override
    public void forEach(Consumer<? super M> action) {
        AssertUtil.nonNull(action);

        lock.readLock().lock();
        try {
            elements.forEach(action);
        } finally {
            lock.readLock().unlock();
        }
    }

    /**
     * 仅支持迭代，不支持修改操作的迭代器
     *
     * @return
     */
    @Override
    public Iterator<M> iterator() {
        return new Iterator<M>() {
            private Iterator<M> inIterator = elements.iterator();

            @Override
            public boolean hasNext() {
                return inIterator.hasNext();
            }

            @Override
            public M next() {
                return inIterator.next();
            }
        };
    }

    @Override
    public Spliterator<M> spliterator() {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Container<?> container = (Container<?>) o;
        return elements.equals(container.elements);
    }

    @Override
    public int hashCode() {
        return Objects.hash(elements);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("[ ");

        lock.readLock().lock();
        try {
            int count = 1;
            for (M inElements : elements) {
                sb.append(count++).append(" : ").append(inElements).append("\n");
            }
        } finally {
            lock.readLock().unlock();
        }
        return sb.append(" ]").toString();
    }
}
